package it.tukano.swingdeco.controls;

import it.tukano.collections.LinkedListExt;
import it.tukano.collections.ListExt;
import it.tukano.swingdeco.ExtUtilities;
import java.util.List;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * A JSpinner extension
 * @author pgi
 */
public class JSpinnerExt extends JSpinner {

    private static final long serialVersionUID = 0;
    private List<ChangeListener> cachedListeners;
    private ListExt<ChangeListener> detachableListeners = new LinkedListExt<ChangeListener>();
    private Class<? extends Number> preferredNumericType = Double.class;

    /**
     * Set a number model for this spinner
     * @param value the actual value of the spinner
     * @param stepSize the step size of the spinner
     * @return this
     */
    public JSpinnerExt withNumericModel(Number value, Number stepSize) {
        if(value instanceof Float) {
            setPreferredNumericType(Float.class);
        } else if(value instanceof Double) {
            setPreferredNumericType(Double.class);
        } else {
            setPreferredNumericType(Integer.class);
        }
        setModel(new SpinnerNumberModel(value, null, null, stepSize));
        return this;
    }

    public <K extends Number> JSpinnerExt setPreferredNumericType(Class<K> type) {
        preferredNumericType = type;
        return this;
    }

    public Class<? extends Number> getPreferredNumericType() {
        return preferredNumericType;
    }

    /**
     * Set a number model for this spinner
     * @param value the actual value of the spinner
     * @param min the minium value of the spinner
     * @param max the maximum value of the spinner
     * @param stepSize the step size of the spinner
     * @return this
     */
    public JSpinnerExt withNumericModel(Number value, Number min, Number max, Number stepSize) {
        setModel(new SpinnerNumberModel(value, min == null ? null : Comparable.class.cast(min), max == null ? null : Comparable.class.cast(max), stepSize));
        return this;
    }

    /**
     * Returns the current value of the spinner as a float. Returns null if the spinner
     * has not a numeric model
     * @return the float value of the spinner
     */
    public Float getFloatValue() {
        Number value = getNumberValue();
        return value == null ? null : value.floatValue();
    }

    /**
     * Returns the current value of the spinner as a double. Returns null if the spinner
     * has not a numeric model
     * @return the double value of the spinner
     */
    public Double getDoubleValue() {
        Number value = getNumberValue();
        return value == null ? null : value.doubleValue();
    }

    public void setFloatValue(float v) {
        setValue((double) v);
    }
    
    public JSpinnerExt setValue(Number n) {
        Number value = getNumberValue();
        if(value instanceof Integer || value instanceof Short || value instanceof Byte) {
            super.setValue(n.intValue());
        } else if(value instanceof Long) {
            super.setValue(n.longValue());
        } else if(value instanceof Float || value instanceof Double) {
            super.setValue(n.doubleValue());
        } else {
            super.setValue(n);
        }
        return this;
    }

    /**
     * Returns the current value of the spinner as a number. Returns null if the spinner
     * has not a numeric model
     * @return the numerico value of the spinner
     */
    public Number getNumberValue() {
        Object value = getValue();
        return value instanceof Number ? ((Number) value) : null;
    }
    
    /**
     * Set the tool tip text for this spinner
     * @param bundleId the resource bundle to use
     * @param bundleKey the resource bundle key
     * @return this
     */
    public JSpinnerExt tip(String bundleId, String bundleKey) {
        ExtUtilities.setToolTipText(this, bundleId, bundleKey);
        return this;
    }

    /**
     * Add a change listener to this spinner
     * @param spinnerListener the listener to add
     * @return this
     */
    public JSpinnerExt withChangeListener(ChangeListener spinnerListener) {
        addChangeListener(spinnerListener);
        detachableListeners.add(spinnerListener);
        return this;
    }

    public JSpinnerExt addListener(final Runnable listener) {
        return withChangeListener(new ChangeListener() {

            public void stateChanged(ChangeEvent e) {
                listener.run();
            }
        });
    }

    /**
     * Sets the value of this editor as a float. 
     * @param value the value to set
     * @param fireEvent if false this spinner will not notify events to the
     * listeners
     */
    public void setFloatValue(Number value, boolean fireEvent) {
        if(!fireEvent) detachListeners();
        setFloatValue(value.floatValue());
        if(!fireEvent) attachListeners();
    }

    public void detachListeners() {
        for (ChangeListener changeListener : detachableListeners) {
            removeChangeListener(changeListener);
        }
    }

    public void attachListeners() {
        for (ChangeListener changeListener : detachableListeners) {
            addChangeListener(changeListener);
        }
        cachedListeners = null;
    }
}
